﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.SessionState;
using System.Web.Configuration;
using System.Configuration;
using MongoDB.Driver;
using MongoDB.Driver.Builders;

namespace A2DFramework.SessionService
{
    public sealed class MongoDBSessionStateStore : SessionStateStoreProviderBase
    {
        private SessionStateSection pConfig;
        private string pApplicationName;

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(name, config);

            pApplicationName =System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
            System.Configuration.Configuration cfg = WebConfigurationManager.OpenWebConfiguration(pApplicationName);
            pConfig =(SessionStateSection)cfg.GetSection("system.web/sessionState");
        }

        public override SessionStateStoreData CreateNewStoreData(System.Web.HttpContext context, int timeout)
        {
            return new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), timeout);
        }

        public override void CreateUninitializedItem(System.Web.HttpContext context, string id, int timeout)
        {
            //insert to db
            MongoDBSessionEntity session = new MongoDBSessionEntity();
            session.ApplicationName = this.pApplicationName;
            session.SessionId = id;
            session.Created = DateTime.Now;
            session.Expires = DateTime.Now.AddMinutes(pConfig.Timeout.Minutes);
            session.LockDate = DateTime.Now;
            session.LockId = 0;
            session.Timeout = timeout;
            session.Locked = false;
            session.Flags = (int)SessionStateActions.InitializeItem;

            MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);
            collection.Save(session);
        }

        public override void Dispose()
        {
        }

        public override void EndRequest(System.Web.HttpContext context)
        {
        }

        public override SessionStateStoreData GetItem(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            return GetSessionStoreItem(false, context, id, out locked, out lockAge, out lockId, out actions);
        }

        public override SessionStateStoreData GetItemExclusive(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            return GetSessionStoreItem(true, context, id, out locked, out lockAge, out lockId, out actions);
        }

        public override void InitializeRequest(System.Web.HttpContext context)
        {
        }

        public override void ReleaseItemExclusive(System.Web.HttpContext context, string id, object lockId)
        {
            //update locked=0, expired=, where lockId=?
            MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);

            var query = Query.And(  Query.EQ("LockId", int.Parse(lockId.ToString())),
                                    Query.EQ("_id", id), 
                                    Query.EQ("ApplicationName", pApplicationName));
            var update = Update.Set("Locked", false)
                                .Set("Expires", DateTime.Now.AddMinutes(pConfig.Timeout.Minutes));

            collection.Update(query, update);
        }

        public override void RemoveItem(System.Web.HttpContext context, string id, object lockId, SessionStateStoreData item)
        {
            //delete where sessionId=? and lockId=? and applicationname=?
            MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);

            var query = Query.And(Query.EQ("LockId", int.Parse(lockId.ToString())),
                                    Query.EQ("_id", id),
                                    Query.EQ("ApplicationName", pApplicationName));
            collection.Remove(query);
        }

        public override void ResetItemTimeout(System.Web.HttpContext context, string id)
        {
            //update expire date
            MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);

            var query = Query.And(Query.EQ("_id", id),
                                    Query.EQ("ApplicationName", pApplicationName));
            var update = Update.Set("Expires", DateTime.Now.AddMinutes(pConfig.Timeout.Minutes));
            collection.Update(query, update);
        }

        public override void SetAndReleaseItemExclusive(System.Web.HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
        {
            MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);
            if (newItem)
            {
                //delete expired items
                var query = Query.And(Query.EQ("_id", id),
                                    Query.EQ("ApplicationName", pApplicationName),
                                    Query.LT("Expires", DateTime.Now));

                collection.Remove(query);

                //insert new item
                MongoDBSessionEntity session = new MongoDBSessionEntity();
                session.ApplicationName = this.pApplicationName;
                session.SessionId = id;
                session.Created = DateTime.Now;
                session.Expires = DateTime.Now.AddMinutes(pConfig.Timeout.Minutes);
                session.LockDate = DateTime.Now;
                session.LockId = 0;
                session.Timeout = item.Timeout;
                session.Locked = false;
                session.Flags = (int)SessionStateActions.None;
                session.SessionItems = Helper.Serialize((SessionStateItemCollection)item.Items);

                collection.Save(session);
            }
            else
            {
                //update item
                var query = Query.And(Query.EQ("_id", id),
                                    Query.EQ("ApplicationName", pApplicationName),
                                    Query.EQ("LockId", int.Parse(lockId.ToString())));
                MongoDBSessionEntity entity= collection.FindOne(query);
                entity.Expires = DateTime.Now.AddMinutes(item.Timeout);
                entity.SessionItems = Helper.Serialize((SessionStateItemCollection)item.Items);
                entity.Locked = false;
                collection.Save(entity);
            }
        }

        public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
        {
            return false;
        }










        private SessionStateStoreData GetSessionStoreItem(bool lockRecord, System.Web.HttpContext context, 
                                                            string id,
                                                            out bool locked,
                                                            out TimeSpan lockAge,
                                                            out object lockId,
                                                            out SessionStateActions actions)
        {
            SessionStateStoreData item = null;  
            lockAge = TimeSpan.Zero;
            lockId = null;
            locked = false;
            actions = 0;

            bool foundRecord = false;
            bool deleteData = false;

            MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);

            if (lockRecord)
            { 
                //update db, set locked=1, lockdate=now
                var query1 = Query.And(Query.EQ("_id", id),
                                    Query.EQ("ApplicationName", pApplicationName),
                                    Query.EQ("Locked", MongoDB.Bson.BsonValue.Create(false)),
                                    Query.GT("Expires", DateTime.UtcNow));

                long count = collection.Find(query1).Count();
                if (count == 0)
                {
                    locked = true;
                }
                else
                {
                    var update = Update.Set("Locked", true).Set("LockDate", DateTime.Now);
                    collection.Update(query1, update);
                    locked = false;
                }
            }
            //get item by id
            var query2 = Query.And(Query.EQ("_id", id),
                                    Query.EQ("ApplicationName", pApplicationName));
            MongoDBSessionEntity entity=collection.FindOne(query2);
            if (entity != null)
            {
                if (entity.Expires < DateTime.Now)
                {
                    locked = false;
                    deleteData = true;
                }
                else
                {
                    foundRecord = true;
                }
            }

            //delete item if session expired
            if (deleteData)
            {
                var query3 = Query.And(Query.EQ("_id", id),
                                    Query.EQ("ApplicationName", pApplicationName));
                collection.Remove(query3);
            }

            if (!foundRecord)
                locked = false;

            if (foundRecord && !locked)
            {
                if (lockId == null)
                    lockId = 0;
                lockId = (int)lockId + 1;

                var query4 = Query.And(Query.EQ("_id", id),
                                    Query.EQ("ApplicationName", pApplicationName));
                var update4 = Update.Set("LockId", (int)lockId)
                                        .Set("Flags", (int)SessionStateActions.None);
                collection.Update(query4, update4);

                if (actions == SessionStateActions.InitializeItem)
                    item = CreateNewStoreData(context, pConfig.Timeout.Minutes);
                else
                    item = Helper.Deserialize(context, entity.SessionItems, entity.Timeout);
            }
            return item;
        }
    }
}